﻿/************************************************************************

   Extended Silverlight Toolkit

   Copyright (C) 2010-2012 Xceed Software Inc.

   This program is provided to you under the terms of the Microsoft Public
   License (Ms-PL) as published at http://extendedsilverlight.codeplex.com/license

   Please purchase a commercial version of this toolkit if you are using
   it in a commercial product. The commercial versions support the project,
   and also include more features for each control, priority support,
   source code, updates, removed watermark, and a proprietary license.

   Visit http://xceed.com and follow @datagrid on Twitter.

  **********************************************************************/

using System;
using System.Collections.Generic;
using System.Linq;

namespace Xceed.Silverlight.Data
{
  internal sealed class ContextEnumerator<T> : AsyncParentEnumerator<T>, IPeekableEnumerator<T>, ICachingEnumerator
  {
    #region Constructor

    internal ContextEnumerator(
      IAsyncEnumerator childEnumerator,
      INotifyDataSourceChanged dataSourceChanged,
      IContextHelper dataHelper )
      : base( childEnumerator )
    {
      if( dataHelper == null )
        throw new ArgumentNullException( "dataHelper" );

      m_dataSourceChanged = dataSourceChanged;
      m_dataHelper = dataHelper;

      if( m_dataSourceChanged != null )
      {
        m_dataSourceChanged.DataSourceChanged += new EventHandler<DataSourceChangedEventArgs>( this.OnDataSourceChanged );
      }
    }

    #endregion

    public override IAsyncResult BeginCount( CountParameters parameters, AsyncCallback userCallback, object asyncState )
    {
      if( parameters.Context == null )
      {
        parameters = new CountParameters(
                       parameters.StartPosition,
                       parameters.TargetGroup,
                       this.GetCurrentContext() );
      }

      return base.BeginCount( parameters, userCallback, asyncState );
    }

    public override IAsyncResult BeginGroupCount( CountParameters parameters, AsyncCallback userCallback, object asyncState )
    {
      if( parameters.Context == null )
      {
        parameters = new CountParameters(
                       parameters.StartPosition,
                       parameters.TargetGroup,
                       this.GetCurrentContext() );
      }

      return base.BeginGroupCount( parameters, userCallback, asyncState );
    }

    public override JumpMoveAndFetchAsyncResult BeginJumpMoveAndFetch( JumpMoveAndFetchParameters parameters, AsyncCallback userCallback, object asyncState )
    {
      if( parameters.Context == null )
      {
        parameters = parameters.CreateWithNewContext( this.GetCurrentContext() );
      }

      return base.BeginJumpMoveAndFetch( parameters, userCallback, asyncState );
    }

    public override IAsyncResult BeginGetStatValues( StatParameters parameters, AsyncCallback userCallback, object asyncState )
    {
      if( parameters.Context == null )
      {
        parameters = new StatParameters(
                       parameters.StatFunctions,
                       parameters.TargetPath,
                       this.GetCurrentContext() );
      }

      return base.BeginGetStatValues( parameters, userCallback, asyncState );
    }

    protected override void Dispose( bool disposing )
    {
      m_disposed = true;

      if( disposing )
      {
        if( m_dataSourceChanged != null )
        {
          m_dataSourceChanged.DataSourceChanged -= new EventHandler<DataSourceChangedEventArgs>( this.OnDataSourceChanged );
        }
      }

      base.Dispose( disposing );
    }

    private DataSourceContext GetCurrentContext()
    {
      return m_dataHelper.GetContext();
    }

    private void OnDataChanged( DataSourceChangedEventArgs e )
    {
      if( ( m_disposed ) || ( e == null ) )
        return;

      if( e.Action == DataSourceChangedAction.Add )
      {
        foreach( T item in e.NewItems.Cast<T>() )
        {
          this.RaiseDataAdd( item );
        }
      }
      else if( e.Action == DataSourceChangedAction.Remove )
      {
        foreach( T item in e.OldItems.Cast<T>() )
        {
          this.RaiseDataRemoved( item, null, null );
        }
      }
      else if( e.Action == DataSourceChangedAction.Replace )
      {
        if( e.NewItems.Count != e.OldItems.Count )
          throw new ArgumentException( "The new item count is different than the old item count.", "e" );

        int count = e.NewItems.Count;
        for( int i = 0; i < count; i++ )
        {
          T oldItem = ( T )e.OldItems[ i ];
          T newItem = ( T )e.NewItems[ i ];

          this.RaiseDataReplaced( oldItem, newItem );
        }
      }
      else if( e.Action == DataSourceChangedAction.ItemChanged )
      {
        foreach( ItemChangedDescription itemChanged in e.ItemsChanged )
        {
          ItemChangedInfo itemChangedInfo;

          if( itemChanged.ChangedProperties.Count == 0 )
          {
            itemChangedInfo = new ItemChangedInfo( itemChanged.Item );
          }
          else
          {
            List<string> propertiesName = new List<string>();
            Dictionary<string, object> oldValues = new Dictionary<string, object>();

            foreach( PropertyChangedDescription propertyChanged in itemChanged.ChangedProperties )
            {
              string propertyName = propertyChanged.PropertyName;
              propertiesName.Add( propertyName );

              object oldValue = propertyChanged.OldValue;
              if( oldValue != PropertyChangedDescription.UnsetValue )
              {
                oldValues.Add( propertyName, oldValue );
              }
            }

            itemChangedInfo = ( oldValues.Count > 0 )
                                ? new ItemChangedInfo( itemChanged.Item, propertiesName )
                                : new ItemChangedInfo( itemChanged.Item, propertiesName, oldValues );
          }

          this.RaiseDataItemChanged( itemChangedInfo );
        }
      }
      else if( e.Action == DataSourceChangedAction.Reset )
      {
        this.RaiseDataReset();
      }
      else
      {
        throw new DataInternalException();
      }
    }

    private void OnDataSourceChanged( object sender, DataSourceChangedEventArgs e )
    {
      if( !m_disposed )
      {
        foreach( DataSourceChangedEventArgs dataSourceChange in this.ChildEnumerator.TransformDataSourceChangedEventArgs( this.GetCurrentContext(), e ) )
        {
          this.OnDataChanged( dataSourceChange );
        }
      }
    }

    #region IPeekableEnumerator<T> Members

    public PeekResult<T> Peek( PeekParameters parameters )
    {
      IPeekableEnumerator<T> peekableEnumerator = this.ChildEnumerator as IPeekableEnumerator<T>;
      if( peekableEnumerator == null )
        return PeekResult<T>.Failure( parameters );

      if( parameters.Context == null )
      {
        parameters = parameters.CreateWithNewContext( this.GetCurrentContext() );
      }

      return peekableEnumerator.Peek( parameters );
    }

    #endregion

    #region ICachingEnumerator Members

    public int CacheSize
    {
      get
      {
        ICachingEnumerator enumerator;
        if( !this.TryGetCachingEnumerator( out enumerator ) )
          return 0;

        return enumerator.CacheSize;
      }
    }

    public void SetCacheSize( int size )
    {
      ICachingEnumerator enumerator;
      if( !this.TryGetCachingEnumerator( out enumerator ) )
        return;

      enumerator.SetCacheSize( size );
    }

    public void SetCacheSize( int size, double topTrimRatio )
    {
      ICachingEnumerator enumerator;
      if( !this.TryGetCachingEnumerator( out enumerator ) )
        return;

      enumerator.SetCacheSize( size, topTrimRatio );
    }

    public void SetCacheSize( int size, SourcePosition topLimitTrim )
    {
      ICachingEnumerator enumerator;
      if( !this.TryGetCachingEnumerator( out enumerator ) )
        return;

      enumerator.SetCacheSize( size, topLimitTrim );
    }

    private bool TryGetCachingEnumerator( out ICachingEnumerator enumerator )
    {
      enumerator = this.ChildEnumerator as ICachingEnumerator;

      return ( enumerator != null );
    }

    #endregion

    #region Private Fields

    private readonly INotifyDataSourceChanged m_dataSourceChanged;
    private readonly IContextHelper m_dataHelper;
    private bool m_disposed; //false

    #endregion
  }
}
